#ifndef INC_MATRIX44_H
#define INC_MATRIX44_H

#include "Vector3.h"

namespace gti
{

class matrix33;
class quaternion;

class matrix44 
{
// column order matrix
// row 1 {11,21,31,41}
// column 1 {11,12,13,14}
// vectors are column ordered too so matrix vector multiplication is
//  11 * v.x + 21 * v.y + 31 * v.z

//  xaxis.x           yaxis.x           zaxis.x          0
//  xaxis.y           yaxis.y           zaxis.y          0
//  xaxis.z           yaxis.z           zaxis.z          0
//  trans.x           trans.y           trans.z          1 

//  11           12           13          14
//  21           22           23          24
//  31           32           33          34
//  41           42           43          44 


public:
	matrix44();
	matrix44(const matrix44& matrix);
	matrix44(
		float a11,float a12,float a13,float a14,
		float a21,float a22,float a23,float a24,
		float a31,float a32,float a33,float a34,
		float a41,float a42,float a43,float a44);
	matrix44(double* data);
	matrix44(
		double a11,double a12,double a13,double a14,
		double a21,double a22,double a23,double a24,
		double a31,double a32,double a33,double a34,
		double a41,double a42,double a43,double a44);
	// data 
	union
	{
            struct
		{
            float        _11, _12, _13, _14;
            float        _21, _22, _23, _24;
            float        _31, _32, _33, _34;
            float        _41, _42, _43, _44;

    };
    float M[4][4];
    float m[16];
    };

	// basic functions
	matrix44& identity();
	matrix44& transpose();
	bool inverse(matrix44& inverse) const;
	float trace() const;
	void getQuaternion(quaternion &q) const;

	// Multiplies the point by the inverse of the current matrix
	// Note: the matrix must be a orthonormal matrix without scaling
	gti::vector3f multInverse(const gti::vector3f& point) const;

	void setNoTranslation();
	void setRow(int row, const vector3f& axis);
	void setColumn(int col, const vector3f& axis);
	void setTranslation(const vector3f& trans);
	void setScale(const vector3f& scale);
	void setRotation(float angle, vector3f axis);

	void setTranslationMatrix(const vector3f& trans);
	void setScaleMatrix(const vector3f& scale);

	vector3f getTranslation() const;
	vector3f getScale();

	void setOrthoProjection(float left, float right, float bottom, float top, float z_near, float z_far);
	void setProjection(float fov, float aspect, float near, float far);

	void copyRow(int ro, vector3f& row) const;
	void copyColumn(int col, vector3f& column) const;
	void copyTranslation(vector3f& trans) const;

	// useful methods
	void set(const float *src);
	void set(const double *src);

	void clear();

	void set(const quaternion& q, const vector3f& t);
	void set(const matrix33& mat, const vector3f& t);

	void setBias();

	// compute the inverse matrix from a rotation unit quaternion
	// and a translation
	void setInverse(const quaternion& q, const vector3f& t);

	// multiplies point only by the rotating part of the matrix
	void rotate(const vector3f& point, vector3f& rpoint) const;

	// multiplies point only by the transposed rotating part of the matrix
	vector3f rotateTransposed(const vector3f& point) const;
	
	// returns the 3x3 matrix with the rotation information
	matrix33 getRotationMatrix() const;

public:
	// up vector can be modified in order to generate a valid orthonormal matrix
	void cameraLookAt(const vector3f& eye, const vector3f& center, vector3f& up);

public:
	// useful operators
	matrix44 operator+(const matrix44 &matrix) const;
	matrix44 operator-(const matrix44 &matrix) const;
	matrix44 operator*(const matrix44 &matrix) const;

	vector4f operator*(const vector4f& v) const;

	void operator+=(const matrix44 &matrix);
	void operator*=(const matrix44 &matrix);
	void operator*=(float f);

	friend vector3f operator*(const matrix44& matrix, const vector3f& vector);
	friend bool operator == (const matrix44& m1, const matrix44& m2);
	friend bool operator != (const matrix44& m1, const matrix44& m2);

	friend matrix44 operator+(const matrix44& matrix, float f);
	friend matrix44 operator+(float f,const matrix44& matrix);
	friend matrix44 operator-(const matrix44& matrix, float f);
	friend matrix44 operator-(float f,const matrix44& matrix);
	friend matrix44 operator*(const matrix44& matrix, float f);
	friend matrix44 operator*(float f,const matrix44& matrix);

	void project(const vector3f& src, vector3f& dst) const;
	
	// returns sum of abs( elems )
	float getDistanceError( const matrix44 &b ) const;

	// useful conversion

	const float &operator[](int i) const {return m[i];}
	float &operator[](int i) {return m[i];}
	operator float*(void) {return m;}
	operator const float*(void) const {return m;}
	float &operator()(int i,int j) {return M[i][j];}
	const float &operator()(int i, int j) const {return M[i][j];}

#ifdef USE_DIRECT3D
	operator const D3DXMATRIX* (void) const {return (const D3DXMATRIX*)m;}
	operator D3DXMATRIX* (void) {return (D3DXMATRIX*)m;}
#endif
};

};//namespace gti

#endif//INC_MATRIX44_H
